﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using MentalAlchemy.Atomics;

namespace MentalAlchemy.Molecules.Image
{
	public abstract class RegionOfInterest
	{
		#region - Public properties. -
		/// <summary>
		/// Ordinal number of the ROI.
		/// </summary>
		public int Number { get; set; }
		#endregion

		#region - Abstract properties. -
		/// <summary>
		/// ROI pretimeter.
		/// </summary>
		public abstract float Perimeter { get; }
		/// <summary>
		/// ROI square.
		/// </summary>
		public abstract float Square { get; } 
		#endregion

		#region - Abstract methods. -
		public abstract RegionOfInterest Clone();

		public abstract void Scale(float factor);
		public abstract void Translate(PointF vector);
		public abstract void Resize(PointF diff, BoundingRectPoint dir, float eps);
		public abstract bool IsBorderPoint(PointF point, float eps);
		public abstract BoundingRectPoint CheckRoiNodeSelection(PointF point, float eps);
		public abstract Stats CalculateStats(IList pixels, Size size, float scale, float eps);
		#endregion

		#region - Public methods. -

		#region - Resize methods. -
		/// <summary>
		/// Resize ROI within the given bounding rectangle using resizing vector and the application points.
		/// </summary>
		/// <param name="rect">Bounding rectangle.</param>
		/// <param name="diff">Resizement vector.</param>
		/// <param name="dir">Resizement application point.</param>
		protected static void Resize(ref RectangleF rect, PointF diff, BoundingRectPoint dir)
		{
			if (dir == BoundingRectPoint.HorizontalResizeLeft
				|| dir == BoundingRectPoint.VerticalResizeUpper
				|| dir == BoundingRectPoint.UpperLeft)
			{
				ResizeUpLeft(ref rect, diff);
			}
			else if (dir == BoundingRectPoint.HorizontalResizeRight
				|| dir == BoundingRectPoint.VerticalResizeLower
				|| dir == BoundingRectPoint.LowerRight)
			{
				ResizeDownRight(ref rect, diff);
			}
			else if (dir == BoundingRectPoint.LowerLeft)
			{
				ResizeDownLeft(ref rect, diff);
			}
			else if (dir == BoundingRectPoint.UpperRight)
			{
				ResizeUpRight(ref rect, diff);
			}
		}

		/// <summary>
		/// Resize ROI bounded by [rect] in the left-up direction along the [diff] vector.
		/// </summary>
		/// <param name="rect">Bounding rectangle.</param>
		/// <param name="diff">Resizement vector.</param>
		protected static void ResizeUpLeft(ref RectangleF rect, PointF diff)
		{
			rect.X += diff.X;
			rect.Y += diff.Y;

			rect.Width -= diff.X;
			rect.Height -= diff.Y;
		}

		/// <summary>
		/// Resize ROI bounded by [rect] in the left-bottom direction along the [diff] vector.
		/// </summary>
		/// <param name="rect">Bounding rectangle.</param>
		/// <param name="diff">Resizement vector.</param>
		protected static void ResizeDownLeft(ref RectangleF rect, PointF diff)
		{
			rect.X += diff.X;
			rect.Height += diff.Y;

			rect.Width -= diff.X;
		}

		/// <summary>
		/// Resize ROI bounded by [rect] in the right-bottom direction along the [diff] vector.
		/// </summary>
		/// <param name="rect">Bounding rectangle.</param>
		/// <param name="diff">Resizement vector.</param>
		protected static void ResizeDownRight(ref RectangleF rect, PointF diff)
		{
			rect.Width += diff.X;
			rect.Height += diff.Y;
		}

		/// <summary>
		/// Resize ROI bounded by [rect] in the right-up direction along the [diff] vector.
		/// </summary>
		/// <param name="rect">Bounding rectangle.</param>
		/// <param name="diff">Resizement vector.</param>
		protected static void ResizeUpRight(ref RectangleF rect, PointF diff)
		{
			rect.Width += diff.X;
			rect.Y += diff.Y;

			rect.Height -= diff.Y;
		}
		#endregion

		#endregion
	}

	public class FlexibleRoi : RegionOfInterest
	{
		private List<PointF> points = new List<PointF>();
		private bool isClosed;

		private PointF selectedPoint = PointF.Empty;

		#region - Properties -
		public List<PointF> Points
		{
			get { return points; }
			set { points = value; }
		}

		public bool IsClosed
		{
			get { return isClosed; }
		}

		public bool ChangeNodesMode { get; set; }

		public override float Perimeter
		{
			get
			{
				float perimeter = 0.0f;

				for (int i = 0; i < points.Count; ++i )
				{
					int j = (i+1) % points.Count;	// cycle points sampling.
					perimeter += StructMath.Distance(points[i], points[j]);
				}

				return perimeter;
			}
		}

		public override float Square
		{
			get
			{
				float res = CalculateSquare(points);
				return res;
			}
		}
		#endregion

		#region - Constructor. -
		public FlexibleRoi() {}
		public FlexibleRoi (FlexibleRoi roi)
		{
			points = new List<PointF>(roi.Points.ToArray());
			Number = roi.Number;
			isClosed = roi.IsClosed;
		}
		#endregion

		#region - Public methods -
		public void CloseRegion()
		{
			isClosed = true;
		}

		public override RegionOfInterest Clone()
		{
			return new FlexibleRoi(this);
		}

		#region - Modify ROI points. -
		public override void Scale(float factor)
		{
			for (int i = 0; i < points.Count; ++i)
			{
				var temp = new PointF((points[i].X * factor),
									(points[i].Y * factor));
				points[i] = temp;
			}
			if (!selectedPoint.IsEmpty)
			{
				selectedPoint = new PointF((selectedPoint.X * factor),
										(selectedPoint.Y * factor));
			}
		}

		public override void Translate(PointF vector)
		{
			for (int i = 0; i < points.Count; ++i)
			{
				var temp = new PointF((points[i].X + vector.X),
										(points[i].Y + vector.Y));
				points[i] = temp;
			}
			if (!selectedPoint.IsEmpty)
			{
				selectedPoint = new PointF(selectedPoint.X + vector.X,
											selectedPoint.Y + vector.Y);
			}
		}

		public override void Resize(PointF diff, BoundingRectPoint dir, float eps)
		{
			if (!selectedPoint.IsEmpty)
			{
				// find the point closest to the [selectedPoint]
				int idx = StructMath.GetClosestPointIndex(points, selectedPoint, eps);
				if (idx >= 0)
				{
					points[idx] = new PointF(points[idx].X + diff.X,
										points[idx].Y + diff.Y);
					selectedPoint = points[idx];
				}
			}
		}

		public void Flip(Orientation orient)
		{
			if (orient == Orientation.Horizontal)
			{
				points = StructMath.FlipHorizontally(points);
			}
			else if (orient == Orientation.Vertical)
			{
				points = StructMath.FlipVertically(points);
			}
			selectedPoint = PointF.Empty;
		}
		#endregion

		/// <summary>
		/// Defines whether given point belongs to the ROI border.
		/// </summary>
		/// <param name="point">Point to consider.</param>
		/// <param name="eps">Accuracy.</param>
		/// <returns></returns>
		public override bool IsBorderPoint(PointF point, float eps)
		{
			for (int i = 1; i < points.Count; ++i)
			{
				if (StructMath.CheckLinePoint(points[i - 1], points[i], point, eps))
					return true;
			}

			// check closing line.
			if (StructMath.CheckLinePoint(points[0], points[points.Count - 1], point, eps))
				return true;
			return false;
		}

		/// <summary>
		/// Checks whether given point corresponds to a ROI's node.
		/// </summary>
		/// <param name="point">Point to consider.</param>
		/// <param name="eps">Accuracy.</param>
		/// <returns></returns>
		public override BoundingRectPoint CheckRoiNodeSelection(PointF point, float eps)
		{
			PointF tempPt = StructMath.GetClosestPoint(points, point, eps);
			if (!tempPt.IsEmpty)
			{
				selectedPoint = tempPt;
				return BoundingRectPoint.All;	// leave cursor as it is.
			}
			
			return BoundingRectPoint.Undefined;	// leave cursor as it is.
		}

		public override Stats CalculateStats(IList pixels, Size size, float scale, float eps)
		{
			// 0. Rescale points coordinates.
			var tempRoi = new FlexibleRoi();
			int count = 0;
			foreach (PointF point in points)
			{
				tempRoi.points.Add(new PointF(point.X / scale, point.Y / scale));
				count++;
			}

			// 0.a. Find max and min coordinate values.
			int top = 0;
			int left = 0;
			int bottom = size.Height;
			int right = size.Width;

			foreach (PointF point in tempRoi.points)
			{
				if (point.X < left)		left = (int)point.X;
				if (point.X > right)	right = (int)point.X;
				if (point.Y < top)		top = (int)point.Y;
				if (point.Y > bottom)	bottom = (int)point.Y;
			}

			#region - Get lines along which the activity is calculated. -
			var lines = new List<Line>();
			for (int i = top; i <= bottom; ++i)
			{
				var line = new Line();
				for (int j = left; j <= right; ++j)
				{
					var tempPoint = new Point(j, i);
					if (tempRoi.IsBorderPoint(tempPoint, eps))
					{
						if (line.Begin == PointF.Empty)
						{
							line.Begin = new PointF(j, i);
							line.End = line.Begin;	// to ensure that both line coordinates are correct.
						}
						else if ((j - line.Begin.X) > eps)
						{
							line.End = new PointF(j, i);

							// Add existing line and start calculation for a new line in the same raw.
							lines.Add(line);
							line = new Line();
							//break;	// line end is found
						}
					}
				}

				if (line.Begin != PointF.Empty)
				{
					lines.Add(line);
				}
			}
			#endregion

			//
			// Calculate number of pixels within the ROI.
			int pixelsCount = 0;
			foreach (Line line in lines)
			{
				pixelsCount += (int)(line.End.X - line.Begin.X + 0.5);
			}

			Stats res;
			if (pixels[0] is byte)
			{
				var data = new byte[pixelsCount];
				//res.MinCount = byte.MaxValue;
				count = 0;
				foreach (var line in lines)
				{
					int rowIdx = (int)line.Begin.Y * size.Width;
					for (var y = (int)(line.Begin.X + 0.5); y <= line.End.X && count < pixelsCount; ++y, ++count)
					{
						data[count] = (byte)pixels[rowIdx + y];
					}
				}
				res = VectorMath.CalculateStats(data);
			}
			else if (pixels[0] is ushort)
			{
				var data = new ushort[pixelsCount];
				count = 0;
				//res.MinCount = ushort.MaxValue;
				foreach (var line in lines)
				{
					int rowIdx = (int)line.Begin.Y * size.Width;
					for (var y = (int)(line.Begin.X + 0.5); y <= line.End.X && (rowIdx + y < pixels.Count) && count < pixelsCount; ++y, ++count)
					{
						data[count] = (ushort)pixels[rowIdx + y];
					}
				}
				res = VectorMath.CalculateStats(data);
			}
			else
			{
				throw new Exception("[CircleRoi.CalculateActivity] exception: Unsupported data type.");
			}

			return res;
		}
		#endregion
	
		protected static float CalculateSquare (List<PointF> pts)
		{
			var points = new List<PointF>(pts);
			points.Add(points[0]);
			//points.Add(points[1]);

			float res = 0.0f;

			// that's right, cycle for [pts.Count] iterations
			for (int i = 0; i < pts.Count; ++i)
			{
				res += (points[i].X * points[i + 1].Y - points[i].Y * points[i + 1].X);
			}
			res = (float)Math.Abs(0.5*res);

			return res;
		}
	}

	public class CircleRoi : RegionOfInterest
	{
		#region - Properties -
		public PointF Corner1 { get; set; }
		public PointF Corner2 { get; set; }

		public override float Perimeter
		{
			get
			{
				var x = (float)Math.Log(2.0, Math.PI * 0.5);

				// calculate half-axes
				float a = (float)(Math.Abs(Corner1.X - Corner2.X) * 0.5),
						b = (float)(Math.Abs(Corner1.Y - Corner2.Y) * 0.5);

				var p = (float)(Math.Pow(a, x) + Math.Pow(b, x));
				p = (float)Math.Pow(p, 1.0 / x);
				return p * 4;
			}
		}
		public override float Square
		{
			get
			{
				// calculate half-axes
				float a = (float)(Math.Abs(Corner1.X - Corner2.X) * 0.5),
						b = (float)(Math.Abs(Corner1.Y - Corner2.Y) * 0.5);

				return (float)(Math.PI * a * b);
			}
		}
		#endregion

		#region - Construction and initialization -
		public CircleRoi() { }
		public CircleRoi(CircleRoi roi)
		{
			Corner1 = new PointF(roi.Corner1.X, roi.Corner1.Y);
			Corner2 = new PointF(roi.Corner2.X, roi.Corner2.Y);
			Number = roi.Number;
		}
		#endregion

		#region - Public methods -
		public override RegionOfInterest Clone()
		{
			return new CircleRoi(this);
		}

		public override void Resize(PointF diff, BoundingRectPoint dir, float eps)
		{
			// create bounding rectangle with proper position of corners.
			RectangleF rect = new Rectangle();
			rect.X = Corner1.X < Corner2.X ? Corner1.X : Corner2.X;
			rect.Y = Corner1.Y < Corner2.Y ? Corner1.Y : Corner2.Y;
			rect.Width = Math.Abs(Corner1.X - Corner2.X);
			rect.Height = Math.Abs(Corner1.Y - Corner2.Y);

			Resize(ref rect, diff, dir);

			// apply resize results to the rectangle corners.
			if (Corner1.X < Corner2.X && Corner1.Y < Corner2.Y)
			{
				var corner1 = new PointF(rect.X, rect.Y);
				var corner2 = new PointF(rect.Right, rect.Bottom);

				Corner1 = corner1;
				Corner2 = corner2;
			}
			else
			{
				var corner1 = new PointF(rect.X, rect.Bottom);
				var corner2 = new PointF(rect.Right, rect.Y);

				Corner1 = corner1;
				Corner2 = corner2;
			}
		}

		public override void Scale(float factor)
		{
			var temp = new PointF((Corner1.X * factor),
								(Corner1.Y * factor));
			Corner1 = temp;

			temp = new PointF((Corner2.X * factor),
							(Corner2.Y * factor));
			Corner2 = temp;
		}

		public override void Translate (PointF vector)
		{
			var temp = new PointF((Corner1.X + vector.X),
								(Corner1.Y + vector.Y));
			Corner1 = temp;

			temp = new PointF((Corner2.X + vector.X),
								(Corner2.Y + vector.Y));
			Corner2 = temp;
		}

		/// <summary>
		/// Defines whether given point belongs to the ROI border.
		/// </summary>
		/// <param name="point">Point to consider.</param>
		/// <param name="eps">Accuracy.</param>
		/// <returns>[True] if the given point belongs to the border and [Flase] otherwise.</returns>
		public override bool IsBorderPoint (PointF point, float eps)
		{
			// check if given point belongs to the ellipse.
            RectangleF rect = new Rectangle();
			rect.X = Corner1.X < Corner2.X ? Corner1.X : Corner2.X;
			rect.Y = Corner1.Y < Corner2.Y ? Corner1.Y : Corner2.Y;
			rect.Width = Math.Abs(Corner1.X - Corner2.X);
			rect.Height = Math.Abs(Corner1.Y - Corner2.Y);

			if (rect.Width > rect.Height)
			{ // ellipse is horizontal
				return StructMath.CheckEllipseBorderHorizontal(rect, point, eps);
			}

			// ellipse is vertical
			return StructMath.CheckEllipseBorderVertical(rect, point, eps);
		}

		/// <summary>
		/// Defines whether given point belongs to the ROI border.
		/// </summary>
		/// <param name="point"></param>
		/// <param name="eps"></param>
		/// <returns></returns>
		public bool IsBorderPoint(Point point, float eps)
		{
			// check if given point belongs to the ellipse.
			RectangleF rect = new Rectangle();
			rect.X = Corner1.X < Corner2.X ? Corner1.X : Corner2.X;
			rect.Y = Corner1.Y < Corner2.Y ? Corner1.Y : Corner2.Y;
			rect.Width = Math.Abs(Corner1.X - Corner2.X);
			rect.Height = Math.Abs(Corner1.Y - Corner2.Y);

			if (rect.Width > rect.Height)
			{ // ellipse is horizontal
				return StructMath.CheckEllipseBorderHorizontal(rect, point, eps);
			}

			// ellipse is vertical
			return StructMath.CheckEllipseBorderVertical(rect, point, eps);
		}

		///// <summary>
		///// See http://ru.wikipedia.org/wiki/Эллипс for details.
		///// </summary>
		///// <param name="rect"></param>
		///// <param name="point"></param>
		///// <returns></returns>
		//private bool CheckBorderHorizontal(RectangleF rect, PointF point, float eps)
		//{
		//    // calculate a and b with offset.
		//    float a = rect.Width / 2, b = rect.Height / 2;

		//    // calculate additional parameters.
		//    float e = Math.Sqrt(1.0 - b * b / (a * a));	// eccentricity.
		//    float rp = a * (1 - e);						// perifocal distance.

		//    // calculate focal points
		//    PointF f1 = new PointF((float)(rect.Left + rp), (float)(rect.Top + b)),
		//            f2 = new PointF((float)(rect.Right - rp), (float)(rect.Top + b));

		//    float dist1 = StructMath.Distance(f1, point),
		//            dist2 = StructMath.Distance(f2, point);
		//    float diff = Math.Abs(dist1 + dist2 - 2 * a);

		//    if (diff < eps) { return true; }

		//    return false;
		//}

		//private bool CheckBorderVertical(RectangleF rect, PointF point)
		//{
		//    // calculate a and b with offset.
		//    float b = rect.Width / 2, a = rect.Height / 2;

		//    // calculate additional parameters.
		//    float e = Math.Sqrt(1.0 - b * b / (a * a));	// eccentricity.
		//    float rp = a * (1 - e);						// perifocal distance.

		//    // calculate focal points
		//    PointF f1 = new PointF((float)(rect.Left + b), (float)(rect.Top + rp)),
		//            f2 = new PointF((float)(rect.Left + b), (float)(rect.Bottom - rp));

		//    float dist1 = GeometryTools.distanceEuclidian(f1, point),
		//            dist2 = GeometryTools.distanceEuclidian(f2, point);
		//    float diff = Math.Abs(dist1 + dist2 - 2 * a);

		//    float eps = 5.0;
		//    if (diff < eps){return true;}

		//    return false;
		//}

		public bool IsBoundingRectPoint (PointF point, float eps)
		{
			var roi = new RectRoi();
			roi.Corner1 = Corner1;
			roi.Corner2 = Corner2;

			return roi.IsBorderPoint(point, eps);
		}

		public override BoundingRectPoint CheckRoiNodeSelection(PointF point, float eps)
		{
			//BoundingRectPoint res = BoundingRectPoint.Undefined;

			var roi = new RectRoi();
			roi.Corner1 = Corner1;
			roi.Corner2 = Corner2;

			return roi.CheckRoiNodeSelection(point, eps);
		}

		//public override ActivityProperties CalculateActivity(Bitmap bmp, float scale)
		//{
		//    if (Corner1 == PointF.Empty || Corner2 == PointF.Empty){return new ActivityProperties();}

		//    PointF corner1 = new PointF((float)(Corner1.X / scale), (float)(Corner1.Y / scale));
		//    PointF corner2 = new PointF((float)(Corner2.X / scale), (float)(Corner2.Y / scale));
		//    CircleRoi tempRoi = new CircleRoi();
		//    tempRoi.Corner1 = corner1;
		//    tempRoi.Corner2 = corner2;

		//    #region - Get lines along which the activity is calculated. -
		//    float top		= corner1.Y > corner2.Y ?	corner2.Y : corner1.Y;
		//    float bottom	= corner1.Y > corner2.Y ?	corner1.Y : corner2.Y;
		//    float left		= corner1.X > corner2.X ?	corner2.X : corner1.X;
		//    float right		= corner1.X > corner2.X ?	corner1.X : corner2.X;

		//    List<Line> lines = new List<Line>();
		//    float epsilon = 1.0;
		//    for (int i = (int)top; i <= bottom; ++i)
		//    {
		//        Line line = new Line();
		//        for (int j = (int)left; j <= right; ++j)
		//        {
		//            Point tempPoint = new Point(j, i);
		//            if (tempRoi.IsBorderPoint(tempPoint, 1.0))
		//            {
		//                if (line.Begin == PointF.Empty)
		//                {
		//                    line.Begin = new PointF(j, i);
		//                    line.End = line.Begin;	// to ensure that both line coordinates are correct.
		//                }
		//                else if ((j - line.Begin.X) > epsilon)
		//                {
		//                    line.End = new PointF(j, i);
		//                    break;	// line end is found
		//                }
		//            }
		//        }

		//        lines.Add(line);
		//    } 
		//    #endregion

		//    ActivityProperties res = new ActivityProperties();
		//    List<byte> bytes = new List<byte>();
		//    ImageTool imgTool = new ImageTool();
		//    List<List<byte>> gray = imgTool.ConvertToGrayscaleTable(bmp);

		//    foreach (Line line in lines)
		//    {
		//        int y = (int)line.Begin.Y;
		//        for (int x = (int)line.Begin.X; x <= line.End.X; ++x )
		//        {
		//            byte temp = gray[x][y];
		//            res.TotalCount += temp;
		//            res.VarianceCount += temp * temp;

		//            if (res.MinCount > temp)
		//            {
		//                res.MinCount = temp;
		//            }
		//            if (res.MaxCount < temp)
		//            {
		//                res.MaxCount = temp;
		//            }

		//            bytes.Add(temp);
		//        }
		//    }

		//    res.MeanCount = res.TotalCount / (float)bytes.Count;
		//    res.VarianceCount /= bytes.Count;
		//    res.VarianceCount -= (res.MeanCount * res.MeanCount);

		//    res.MedianCount = StatTools.Median(bytes);

		//    return res;
		//}

		public override Stats CalculateStats(IList pixels, Size size, float scale, float eps)
		{
			if (Corner1 == PointF.Empty || Corner2 == PointF.Empty){return new Stats();}

			var tempRoi = new CircleRoi();
			tempRoi.Corner1 = new PointF((Corner1.X / scale), (Corner1.Y / scale));
			tempRoi.Corner2 = new PointF((Corner2.X / scale), (Corner2.Y / scale));

			#region - Get lines along which the activity is calculated. -
			var top		= tempRoi.Corner1.Y > tempRoi.Corner2.Y ? tempRoi.Corner2.Y : tempRoi.Corner1.Y;
			var bottom	= tempRoi.Corner1.Y > tempRoi.Corner2.Y ? tempRoi.Corner1.Y : tempRoi.Corner2.Y;
			var left	= tempRoi.Corner1.X > tempRoi.Corner2.X ? tempRoi.Corner2.X : tempRoi.Corner1.X;
			var right	= tempRoi.Corner1.X > tempRoi.Corner2.X ? tempRoi.Corner1.X : tempRoi.Corner2.X;

			var lines = new List<Line>();
			//float epsilon = 1.0;
			for (var i = (int)top; i <= bottom; ++i)
			{
				var line = new Line();
				for (var j = (int)left; j <= right; ++j)
				{
					var tempPoint = new Point(j, i);
					if (tempRoi.IsBorderPoint(tempPoint, 1.0f))
					{
						if (line.Begin == PointF.Empty)
						{
							line.Begin = new PointF(j, i);
							line.End = line.Begin;	// to ensure that both line coordinates are correct.
						}
						else if ((j - line.Begin.X) > eps)
						{
							line.End = new PointF(j, i);
							break;	// line end is found
						}
					}
				}

				lines.Add(line);
			} 
			#endregion

			//
			// calculate number of pixels within the ROI.
			int pixelsCount = 0;
			foreach (Line line in lines) {pixelsCount += (int)(line.End.X - line.Begin.X + 0.5);}

			Stats res;

			if (pixels[0] is byte)
			{
				var data = new byte[pixelsCount];
				int count = 0;
				foreach (var line in lines)
				{
					int rowIdx = (int)(line.Begin.Y + 0.5) * size.Width;
					for (var y = (int)(line.Begin.X + 0.5); y <= line.End.X && count < pixelsCount; ++y, ++count)
					{
						data[count] = (byte)pixels[rowIdx + y];
					}
				}
				res = VectorMath.CalculateStats(data);
			}
			else if (pixels[0] is ushort)
			{
				var data = new ushort[pixelsCount];
				int count = 0;
				//res.MinCount = ushort.MaxValue;
				foreach (var line in lines)
				{
					//int y = (int)line.Begin.Y;
					int rowIdx = (int)(line.Begin.Y + 0.5) * size.Width;
					for (var y = (int)(line.Begin.X + 0.5); y <= line.End.X && count < pixelsCount; ++y, ++count)
					{
						data[count] = (ushort)pixels[rowIdx + y];
					}
				}
				res = VectorMath.CalculateStats(data);
			}
			else
			{
				throw new Exception("[CircleRoi.CalculateActivity] exception: Unsupported data type.");
			}

			return res;
		}
		#endregion
	}

	/// <summary>
	/// Rectangular ROI class.
	/// </summary>
	public class RectRoi : RegionOfInterest
	{
		#region - Properties -
		public PointF Corner1 { get; set; }
		public PointF Corner2 { get; set; }

		public override float Perimeter
		{
			get
			{
				float perimeter = Math.Abs(Corner1.X - Corner2.X) * 2 + Math.Abs(Corner1.Y - Corner2.Y) * 2;
				return perimeter;
			}
		}
		public override float Square
		{
			get
			{
				float square = Math.Abs(Corner1.X - Corner2.X) * Math.Abs(Corner1.Y - Corner2.Y);
				return square;
			}
		}
		#endregion

		#region - Construction and initialization -
		public RectRoi() { }
		public RectRoi(RectRoi roi)
		{
			Corner1 = new PointF(roi.Corner1.X, roi.Corner1.Y);
			Corner2 = new PointF(roi.Corner2.X, roi.Corner2.Y);
			Number = roi.Number;
		}
		#endregion

		#region - Public methods -
		public override RegionOfInterest Clone()
		{
			return new RectRoi(this);
		}

		public override void Scale(float factor)
		{
			var temp = new PointF((Corner1.X * factor),
								(Corner1.Y * factor));
			Corner1 = temp;

			temp = new PointF((Corner2.X * factor),
							(Corner2.Y * factor));
			Corner2 = temp;
		}

		public override void Translate (PointF vector)
		{
			var temp = new PointF((Corner1.X + vector.X),
									(Corner1.Y + vector.Y));
			Corner1 = temp;

			temp = new PointF((Corner2.X + vector.X),
								(Corner2.Y + vector.Y));
			Corner2 = temp;
		}

		public override void Resize(PointF diff, BoundingRectPoint dir, float eps)
		{
			// create bounding rectangle with proper position of corners.
			RectangleF rect = new Rectangle();
			rect.X = Corner1.X < Corner2.X ? Corner1.X : Corner2.X;
			rect.Y = Corner1.Y < Corner2.Y ? Corner1.Y : Corner2.Y;
			rect.Width = Math.Abs(Corner1.X - Corner2.X);
			rect.Height = Math.Abs(Corner1.Y - Corner2.Y);

			Resize (ref rect, diff, dir);

			// apply resize results to the rectangle corners.
			if (Corner1.X < Corner2.X && Corner1.Y < Corner2.Y)
			{
				var corner1 = new PointF(rect.X, rect.Y);
				var corner2 = new PointF(rect.Right, rect.Bottom);

				Corner1 = corner1;
				Corner2 = corner2;
			}
			else
			{
				var corner1 = new PointF(rect.X, rect.Bottom);
				var corner2 = new PointF(rect.Right, rect.Y);

				Corner1 = corner1;
				Corner2 = corner2;
			}
		}

		/// <summary>
		/// Defines whether given point belongs to the ROI border.
		/// </summary>
		/// <param name="point">Point ot consider.</param>
		/// <param name="eps">Accuracy.</param>
		/// <returns></returns>
		public override bool IsBorderPoint(PointF point, float eps)
		{
			RectangleF rect = new Rectangle();
			rect.X = Corner1.X < Corner2.X ? Corner1.X : Corner2.X;
			rect.Y = Corner1.Y < Corner2.Y ? Corner1.Y : Corner2.Y;
			rect.Width = Math.Abs(Corner1.X - Corner2.X);
			rect.Height = Math.Abs(Corner1.Y - Corner2.Y);

			float diffX1 = Math.Abs(point.X - Corner1.X),
					diffX2 = Math.Abs(point.X - Corner2.X);
			float diffY1 = Math.Abs(point.Y - Corner1.Y),
					diffY2 = Math.Abs(point.Y - Corner2.Y);
			// if point is close to either vertical OR horizontal border AND is within [rect] dimensions then return [true]
			if (((diffX1 < eps || diffX2 < eps) && (point.Y >= (rect.Y - eps) && point.Y <= (rect.Bottom + eps)))
				||
				(diffY1 < eps || diffY2 < eps) && (point.X >= (rect.X - eps) && point.X <= (rect.Right + eps)))
			{
				return true;
			}
			return false;
		}

		public override BoundingRectPoint CheckRoiNodeSelection(PointF point, float eps)
		{
			var res = BoundingRectPoint.Undefined;

			RectangleF rect = new Rectangle();
			rect.X = Corner1.X < Corner2.X ? Corner1.X : Corner2.X;
			rect.Y = Corner1.Y < Corner2.Y ? Corner1.Y : Corner2.Y;
			rect.Width = Math.Abs(Corner1.X - Corner2.X);
			rect.Height = Math.Abs(Corner1.Y - Corner2.Y);

			float diffX1 = Math.Abs(point.X - Corner1.X),
					diffX2 = Math.Abs(point.X - Corner2.X);
			float diffY1 = Math.Abs(point.Y - Corner1.Y),
					diffY2 = Math.Abs(point.Y - Corner2.Y);

			bool hBound = false, vBound = false;

			// if point is close to vertical bound AND is within [rect] dimensions then set flag to [true]
			if ((diffX1 < eps || diffX2 < eps) && (point.Y >= (rect.Y - eps) && point.Y <= (rect.Bottom + eps)))
			{
				vBound = true;
			}

			// if point is close to horizontal bound AND is within [rect] dimensions then set flag to [true]
			if ((diffY1 < eps || diffY2 < eps) && (point.X >= (rect.X - eps) && point.X <= (rect.Right + eps)))
			{
				hBound = true;
			}

			float halfX = rect.X + rect.Width*0.5f;
			float halfY = rect.Y + rect.Height * 0.5f;

			if (hBound && vBound)
			{	// the point is somewhere in the corner.
				if (point.X <= halfX)
				{	// left
					res = point.Y <= halfY ? BoundingRectPoint.UpperLeft : BoundingRectPoint.LowerLeft;
				}
				else
				{	// right
					res = point.Y <= halfY ? BoundingRectPoint.UpperRight : BoundingRectPoint.LowerRight;
				}
			}
			else if (hBound)
			{	// vertical resize point.
				var diffX = Math.Abs(point.X - halfX);
				if (diffX < eps)
				{
					res = point.Y <= halfY ? BoundingRectPoint.VerticalResizeUpper : BoundingRectPoint.VerticalResizeLower;
				}
			}
			else if (vBound)
			{	// horizontal resize point.
				var diffY = Math.Abs(point.Y - halfY);
				if (diffY < eps)
				{
					res = point.X <= halfX ? BoundingRectPoint.HorizontalResizeLeft : BoundingRectPoint.HorizontalResizeRight;
				}
			}

			return res;
		}

		public override Stats CalculateStats(IList pixels, Size size, float scale, float eps)
		{
			var corner1 = new PointF((Corner1.X / scale), (Corner1.Y / scale));
			var corner2 = new PointF((Corner2.X / scale), (Corner2.Y / scale));

			Stats activity;
			int square = ((int)(corner2.Y - corner1.Y + 0.5) + 1)
						* ((int)(corner2.X - corner1.X + 0.5) + 1);
			if (pixels[0] is ushort)
			{
				corner1.Y = Math.Max(corner1.Y, 0.0f);
				corner1.X = Math.Max(corner1.X, 0.0f);

				var data = new ushort[square];
				int count = 0;
				for (var i = (int)(corner1.Y + 0.5); i <= corner2.Y && i < size.Height; ++i)
				{
					var rowIdx = i * size.Width;
					for (var j = (int)(corner1.X + 0.5); j <= corner2.X && j < size.Width && count < square; ++j, ++count)
					{
						data[count] = (ushort)pixels[rowIdx + j];
					}
				}
				activity = VectorMath.CalculateStats(data);
			} 
			else if (pixels[0] is byte)
			{
				var data = new byte[square];
				var count = 0;
				for (var i = (int)(corner1.Y + 0.5); i <= corner2.Y && i < size.Height; ++i)
				{
					var rowIdx = i * size.Width;
					for (var j = (int)(corner1.X + 0.5); j <= corner2.X && j < size.Width && count < square; ++j, ++count)
					{
						data[count] = (byte)pixels[rowIdx + j];
					}
				}
				activity = VectorMath.CalculateStats(data);
			}
			else if (pixels[0] is int)
			{
				var data = new int[square];
				var count = 0;
				for (var i = (int)(corner1.Y + 0.5); i <= corner2.Y && i < size.Height; ++i)
				{
					var rowIdx = i * size.Width;
					for (var j = (int)(corner1.X + 0.5); j <= corner2.X && j < size.Width && count < square; ++j, ++count)
					{
						data[count] = (int)pixels[rowIdx + j];
					}
				}
				activity = VectorMath.CalculateStats(data);
			}
			else
			{
				throw new Exception("[CircleRoi.CalculateActivity] exception: Unsupported data type.");
			}

			return activity;
		}
		#endregion
	}
}
